package org.nebuloop.photo.organizer;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.nebuloop.photo.organizer.dao.ImageDAO;

/**
 *
 */
public class SequentialImageOrganizer implements ImageOrganizer {

  private static final Logger LOGGER = Logger.getLogger(SequentialImageOrganizer.class.getCanonicalName());
  private final int MAX_PROCESS;
  private boolean skipBadFiles = false;
  private final AtomicLong processedImagesCount = new AtomicLong(0);
  private final AtomicLong skippedFileCount = new AtomicLong(0);
  private String destinationDirectory;
  private final ImageProcessor imageProcessor = new ImageProcessor();
  final Properties sourceProperties = new Properties();
  private boolean isInterrupted;
  private String skippedFilesDestinationDirectory;
  private static final FileFilter imageFileFilter = new FileFilter() {
    private final Set<String> acceptedFilesExt = new HashSet<>(Arrays.asList(".jpg", ".mpg", ".mpeg", ".mp4", ".3gp",
        ".mov"));

    @Override
    public boolean accept(File file) {
      if (!file.isFile()) {
        return false;
      }
      String name = file.getName();
      if (name == null || name.length() < 4) {
        LOGGER.log(Level.WARNING, "Ignoring invalid file : {0}", file.getAbsolutePath());
        return false;
      }
      int i = name.lastIndexOf(".");
      if (i <= 0) {
        LOGGER.log(Level.WARNING, "Ignoring invalid file : {0}", file.getAbsolutePath());
        return false;
      }
      String ext = name.substring(i).toLowerCase();
      return (acceptedFilesExt.contains(ext));
    }
  };
  private static final FileFilter dirFilter = new FileFilter() {
    @Override
    public boolean accept(File arg0) {
      return arg0.isDirectory();
    }
  };

  public SequentialImageOrganizer() {
    MAX_PROCESS = -1;
  }

  public SequentialImageOrganizer(int MAX_PROCESS) {
    this.MAX_PROCESS = MAX_PROCESS;
  }

  public void init(ImageDAO imageDao, boolean preserveNonNumericImageLeafFolderName,
      boolean useCameraInfolderName,
      String skippedFilesDestinationDirectory) throws Exception {
    imageProcessor.setDestinationDirectory(destinationDirectory);
    imageProcessor.setImageDao(imageDao);
    imageProcessor.setPreserveNonNumericFolderName(preserveNonNumericImageLeafFolderName);
    imageProcessor.setUseCameraInfolderName(useCameraInfolderName);
    imageProcessor.setSkippedFilesDestinationDirectory(skippedFilesDestinationDirectory);
    isInterrupted = false;
  }

  public void processDir(File path) throws IOException {
    LOGGER.log(Level.INFO, "Processing top level directory : {0}", path.getAbsolutePath());

    File srcInfo = new File(path, "image_source.info");
    if (srcInfo.exists()) {
      LOGGER.log(Level.INFO, "Image Source Info file {0} exists. Will try to use that file", srcInfo.getAbsolutePath());
      try (FileInputStream fin = new FileInputStream(srcInfo)) {
        sourceProperties.load(fin);
      } catch (Exception ex) {
        LOGGER.log(Level.WARNING, "Unable to source properties from " + srcInfo.getAbsolutePath(), ex);
      }
    }

    String srcId = sourceProperties.getProperty("source.id");
    if (StringUtils.isNotBlank(srcId)) {
      LOGGER.log(Level.INFO, "Image Source id {0}", srcId);
      imageProcessor.setSourceId(srcId);
    } else {
      InetAddress localhost = InetAddress.getLocalHost();
      String localHostname = localhost.getHostName();
      String defaultId = localHostname + "-" + path.getAbsolutePath();
      LOGGER.log(Level.WARNING, "Image Source id is null. Using default value {0}", defaultId);
      imageProcessor.setSourceId(defaultId);
    }
    processFiles(path);
  }

  private void processFiles(File path) throws IOException {
    LOGGER.log(Level.INFO, "Processing dir : {0}", path.getAbsolutePath());
    if (shouldStop()) {
      return;
    }
    File[] image_files = path.listFiles(imageFileFilter);
    int x = 0;
    for (File imageFile : image_files) {
      processedImagesCount.incrementAndGet();
      imageProcessor.processFile(imageFile);
      if (shouldStop()) {
        return;
      }
      x++;
      if (x >= 50) {
        x = 0;
        LOGGER.log(Level.INFO, "Processed {0} images", processedImagesCount.get());
      }
    }
    File[] subdirs = path.listFiles(dirFilter);
    for (File dir : subdirs) {
      if (shouldStop()) {
        return;
      }
      if (dir.getName().startsWith("_")) {
        LOGGER.log(Level.WARNING, "Skipping directory (_ in name) : {0}", dir.getAbsolutePath());
        continue;
      }
      processFiles(dir);
    }
  }

  private boolean shouldStop() {
    if (isInterrupted) {
      LOGGER.log(Level.WARNING, "ImageOrganizer is interrupted...exiting");
      return true;
    }
    if (MAX_PROCESS < 0) {
      // no limit always return false
      return false;
    }
    if (processedImagesCount.intValue() >= MAX_PROCESS) {
      LOGGER.log(Level.WARNING,
          "File processing threshold already crossed. Processed : {0}; Limit : {1}....exiting....", new Object[] {
              processedImagesCount.intValue(), MAX_PROCESS });
      return true;
    }
    return false;
  }

  public String getDestinationDirectory() {
    return destinationDirectory;
  }

  public void setDestinationDirectory(String destinationDirectory) {
    this.destinationDirectory = destinationDirectory;
  }

  public long getProcessedImagesCount() {
    return processedImagesCount.get();
  }

  public long getSkippedFileCount() {
    return skippedFileCount.get();
  }

  public boolean isSkipBadFiles() {
    return skipBadFiles;
  }

  public void setSkipBadFiles(boolean flag) {
    this.skipBadFiles = flag;
  }

  public String getSkippedFilesDestinationDirectory() {
    return skippedFilesDestinationDirectory;
  }

  public void setSkippedFilesDestinationDirectory(String skippedFilesDestinationDirectory) {
    this.skippedFilesDestinationDirectory = skippedFilesDestinationDirectory;
  }

  public void interrupt() {
    this.isInterrupted = true;
    LOGGER.log(Level.WARNING, "ImageOrganizer is interrupted");
  }

  public void clearInterrupt() {
    this.isInterrupted = false;
  }

  public void setSkippedFileLogger(SkippedFileLogger skippedFileLogger) {
    imageProcessor.setSkippedFileLogger(skippedFileLogger);

  }
}
